"""This module defines the PyMak base Action class.
"""

# Python imports
import sys
import os
import logging

# Package imports
import path
import variant
import target
import pmutils

__all__ = [
    'Action',
    'CommandAction',
    'ShellAction',
    ]


class Action(object):
    "This class is the abstract base class for actions."
    
    def execute_action(self, target, variant_values):
        """ This method forms the heart of executing actions that have
        been defined to work with a particular rule.  Sub-classes must
        override this. This method should raise a PyMakException if
        there's an error.
        """
        assert False, "This virtual method should be overridden"


class CommandAction(Action):
    """This class derives from Action. It implements an execute_action
    method that runs a command in the system shell. A non-zero return
    status from that command will cause a PyMakException to be
    raised. The command run is generated by calling the method
    build_command. Derived classes will probably need to override that
    method rather than this one.

    If the cleanup_command method is overridden to return a command
    string, this command will be run after the main command.
    """

    def __init__(self, out_strm=None):
        self.out_strm = out_strm
    
    def execute_action(self, target, variant_values):
        """This method gets a shell command to execute by calling
        build_command. It then expands variables within that command
        and then executes that command in a subshell.

        If the method cleanup_command returns a string, that command
        is then executed.

        Any execptions raised due to the command failing are passed on
        up.
        """
        logging.warning("%s...", target.path)

        cmd = self.build_command(target, variant_values)
        self._execute_action(cmd, target, variant_values)

        cmd = self.cleanup_command(target, variant_values)
        if cmd:
            self._execute_action(cmd, target, variant_values)

    def _execute_action(self, cmd, target, variant_values):
        cmd = pmutils.expand_variables(cmd, variant_values, target._context, target)

        logging.info(cmd)
        try:
            out, err = pmutils.exec_command(cmd, self.out_strm)

        except pmutils.CommandFailedException, e:
            # if we're in keep-going mode, just log the error
            if target._context.keep_going:
                logging.error(_("Ignoring failed command for target %s"), target.path)
                logging.error(str(e))
            else:
                # otherwise let the top level build() call handle printing the error
                raise


    def build_command(self, target, variant_values):
            """ The above implementation of execute_action uses this method to
            generate the string that is the command to be executed. In most cases
            concrete classes derived from this one will override this method to
            return the real command.
            """
            assert False, "Virtual method called"

    def cleanup_command(self, target, variant_values):
        return None



class ShellAction(CommandAction):
    """This class derives from CommandAction. It simply gets a command
    at construction time, and uses this as the shell command to run.

    If the supplied command is a list, array or iterable, then the
    commands are executed in turn in a separate shell.

    If the command consists of multiple lines, then it is split onto
    multiple commands and each commad run in a separate shell.
    """
    def __init__(self, cmd, log_out_and_err=False, cleanup_cmd=None):
        if log_out_and_err:
            strm = sys.stdout
        else:
            strm = None
        super(ShellAction, self).__init__(strm)
        self.cmd = cmd
        self.cleanup_cmd = None

    def build_command(self, target, variant_values):
        return self.cmd

    def cleanup_command(self, target, variant_values):
            return self.cleanup_cmd

    def execute_action(self, target, variant_values):
        """This method overrides the method in CommandAction. It gets
        allows the command to be a list of commands, and each one is
        run in turn. After all the commands are run, any cleanup
        command is finally executed."""
        
        cmds = self.build_command(target, variant_values)
        logging.warning("%s...", target.path)
        if isinstance(cmds, basestring):
            cmds = cmds.split('\n')
        for cmd in cmds:
            self._execute_action(cmd, target, variant_values)
        
        cmd = self.cleanup_command(target, variant_values)
        if cmd:
            self._execute_action(cmd, target, variant_values)
    
